/**
 * Jim Dog JavaScript Platform
 * @auhtor ReGeDa
 * @version 0.0.1
 */

(function() {

	/**
	 * Abstract jDog Class
	 * @author ReGeDa
	 */
	window.jClass = {
		// copy source to destination
		copy: function(d, s) {
			for(var p in s)
				d[p] = s[p];
			return d;
		},
		// extends class by object
		extend: function(clazz, object) {
			return jClass.copy(clazz.prototype, object);
		}
	};

	/**
	 * Check for jDog instance
	 */
	function isDog(obj) {
		return obj instanceof jDog;
	}

	/**
	 * Global Language Object
	 */
	window.jLang = {};

	/**
	 * Elements cache
	 */
	var eCache = {};

	/**
	 * Global jDog Class
	 * @author ReGeDa
	 */
	window.$ = window.jDog = (function(context) {

		if (isDog(this)) {

			//////////////////////////////////////// OBJECT

			var $SELF = this;

			// try create a new DOM element
			if (typeof(context) == "string")
				context = document.createElement(context);

			// return wrapper context
			this.context = function() {
				return context;
			};

			// next sibling
			this.next = function(){
				return $(context.nextSibling);
			};

			// previous sibling
			this.prev = function() {
				return $(context.previousSibling);
			};

			// first child
			this.first = function() {
				return $(context.firstChild);
			};

			// last child
			this.last = function() {
				return $(context.lastChild);
			};

			// child by index
			this.child = function(index) {
				return $(context.childNodes[index]);
			};

			// parent from level
			this.parent = function(level) {
				level = level || 1;

				var p = context.parentNode;

				while(--level > 0)
					p = p.parentNode;

				return $(p);
			};

			// node name
			this.name = function() {
				return context.nodeName.toLowerCase();
			};

			// is text node
			this.isTextNode = function() {
				return context.nodeType == 3;
			};

			// set HTML
			this.html = function(inner, append) {
				if (typeof(inner) == "string") {
					if (append)
						context.innerHTML += inner;
					else
						context.innerHTML = inner;
					// cascade this
					return $SELF;
				}
				else
					return context.innerHTML;
			};

			// set attributes
			// @param name - String|Array
			// @param value - String|Number
			this.attr = function(name, value) {
				// single attribute
				if (typeof(name) == "string") {
					// get value
					if (typeof(value) == "undefined")
						return context.getAttribute(name);
					// set value
					else
						context.setAttribute(name, value);
				}
				// attributes object
				else for(var p in name)
					context.setAttribute(p, name[p]);
				// cascade this
				return $SELF;
			};

			// set style
			// @param name - String|Array
			// @param value - String|Number
			this.css = function(name, value) {
				if (typeof(name) == "string") {
					// getter
					if (typeof(value) == "undefined") {
						// result value
						var v;

						// cross-browser OPACITY getter
						if (name == "xopacity") {
							v = navigator.isIE ?
								// Internet Explorer
								(function() {
									var filter = /\(opacity=(\d+)\)$/.exec($SELF.css("filter"));
									return $.empty(filter) ? '' : filter[1];
								})()
								// W3C
								// converted to string for compatibility with 'jd-animation'
								: $SELF.css("opacity").floatval()*100 + "";
						}
						// standart getter
						else {
							v = context.style[name];
							if ($.empty(v)) {
								if (window.getComputedStyle) {
									v = window
											.getComputedStyle(context, null)
											.getPropertyValue(
												name.replace(/([^A-Z]+)([A-Z])/g, "$1-$2").toLowerCase()
											);
								}
								else if (context.currentStyle)
									v = context.currentStyle[name];

								context.style[name] = v;
							}
						}
						return v;
					}
					// cross-browser OPACITY setter
					else if (name == "xopacity") {
						var p = "opacity";
						if (navigator.isIE){
							p = "filter";
							value = "progid:DXImageTransform.Microsoft.Alpha(opacity="+value+")";
						}
						else
							value /= 100;

						// save
						context.style[p] = value;
					}
					// standart style setter
					else
						context.style[name] = value;
				}
				// multiple setter
				else
					jClass.copy(context.style, name);
				// cascade this
				return $SELF;
			};

			/**
			 * Wrap properties for read only
			 * Provided by client
			 */
			["width","height","top","left"].each(function(type) {
				$SELF[type] = function() {
					return context["client" + type.ucfirst()];
				};
			});

			// set/get class
			this.clazz = function(className, append) {
				if (typeof(className) == "string") {
					if (append)
						context.className += ' ' + className;
					else
						context.className = className;
					// cascade this
					return $SELF;
				}
				else
					return context.className;
			};

			// remove class from element
			this.rclazz = function(className) {
				// replace class to empty string
				context.className.replace(new RegExp("\\s*"+className), '');
				// cascade self
				return $SELF;
			};

			$SELF.callback = {};

			// attach an event
			this.bind = function(type, handler) {

				// multiple setter
				if (typeof(type) != "string") {

					type.each(function(t) {
						$SELF.bind(t, handler);
					});
				}
				// add listener
				else {

					$SELF.callback[type] = handler;

					// mouse wheel event
					if (type == "mousewheel") {

						if (navigator.isFF)
							type = "DOMMouseScroll";

						$SELF.callback[type] = function(event) {
							var delta = 0;
							if (event.wheelDelta)
								delta = event.wheelDelta / 120;
							else if (event.detail)
								delta = -event.detail / 3;
							// process delta
							return handler(event, delta);
						};
					}

					// wrapper
					var listener = function(event) {

						if ( ! event)
							event = window.event;

						// prevent default
						event.deny = function() {
							if (event.stopPropagation) event.stopPropagation();
							if (event.preventDefault) event.preventDefault();
							event.cancelBubble = true;
							event.cancel = true;
							event.returnValue = false;
							return false;
						};

						// set proxy
						$SELF.proxy = $SELF.callback[type];
						// process
						return $SELF.proxy(event);
					};

					// add listener
					if (context.addEventListener)
						context.addEventListener(type, listener, false);
					else if (context.attachEvent)
						context.attachEvent("on" + type, listener);
					else
						context["on" + type] = listener;
				}
				// cascade this
				return $SELF;
			};

			// create node element
			// @private
			function jCreateNode(tagName, attr) {
				var e = new $(tagName);

				if (typeof(attr) == "object")
					e.attr(attr);

				return e;
			}

			// append a child node in end
			this.append = function(tagName, attr) {
				var e = jCreateNode(tagName, attr);
				context.appendChild(e.context());
				return e;
			};

			// insert a child node in top
			this.insert = function(tagName, attr) {
				var t = $SELF.first();
				if (t == false)
					return $SELF.append(tagName, attr);
				else {
					var e = jCreateNode(tagName, attr);
					context.insertBefore(e.context(), t.context());
					return e;
				}
			};

			// remove a child element
			this.remove = function(child) {
				if (isDog(child))
					child = child.context();

				// remove from cache
				if (eCache["CACHE" + child.id])
					eCache["CACHE" + child.id] = null;

				// remove a child
				context.removeChild(child);
				// cascade this
				return $SELF;
			};

			// make a suicide...
			this.die = function() {
				$SELF.parent().remove(context);
				$SELF = null;
			};

			// clear inner
			this.flush = function() {
				context.innerHTML = '';
				// cascade this
				return $SELF;
			};

			// load image ONLY in <img> context
			// handlers - before|after
			this.loadImg = function(src, handlers) {

				handlers = jClass.copy(
					{before: $.blank(), after: $.blank()},
					handlers
				);

				// event
				handlers.before();

				src += (src.indexOf('?') == -1 ? '?' : '&') + Number.random();

				$.createImage(src, function() {
					// show an image
					$SELF.attr("src", src);
					// event
					handlers.after();
				});
			};

			// set/get value
			this.val = function(value) {
				// setter
				if (typeof(value) != "undefined") {

					// to Form Element
					if (typeof(context.value) != "undefined")
						context.value = value;
					// otherwise
					else
						context.nodeValue = value;

					return $SELF;
				}
				// getter
				return typeof(context.value) != "undefined" ? context.value : context.nodeValue;
			};

			// exit
			return $SELF;
		}
		else {

			//////////////////////////////////////// FUNCTION

			// fetch by ID
			if (typeof(context) == "string") {

				// read from cache
				if (eCache["CACHE" + context])
					return eCache["CACHE" + context];

				// browse an element
				var e = document.getElementById(context);
				// result
				return e ? eCache["CACHE" + context] = new $(e) : false;
			}
			// is jDog context
			else if (isDog(context))
				return context;

			// create a DOM element wrapper
			return new $(context);
		}
	});

	/**
	 * AJAX
	 * @author ReGeDa
	 */
	function Transport() {

		var ajax;

		// fetch response result
		function response() {
			var r;
			var h = ajax.getResponseHeader("Content-Type");
			if (h.indexOf("text/xml") != -1)
				r = ajax.responseXML;
			else {
				r = ajax.responseText;
				if (h.indexOf("application/json") != -1)
					r = r.json();
			}
			return r;
		}

		// hello Server!!!
		this.request = function(uri, prop) {

			// properties
			prop = jClass.copy(
				{	// properties
					query: '',
					async: true,
					// event
					oncomplete: $.blank(),
					onwaiting: $.blank(),
					onload: $.blank(),
					onloading: $.blank(),
					onfailure: $.blank()
				},
				prop
			);

			// request
			ajax.open("POST", uri, prop.async);
			ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
			if (prop.async)
				ajax.onreadystatechange = function () {
					switch(ajax.readyState) {
						// complete
						case 4: prop.oncomplete(response(), ajax.status); break;
						// waiting
						case 3: prop.onwaiting(); break;
						// loaded
						case 2: prop.onload(); break;
						// loading
						case 1: prop.onloading(); break;
						// failure initialization
						default: prop.onfailure();
					}
				};
			ajax.send(prop.query);

			// process sync request
			if ( ! prop.async)
				prop.oncomplete(response(), ajax.status);
		};

		// create transport handle
		ajax = $.tryEach(
			function() { return new XMLHttpRequest() },
			function() { return new ActiveXObject("Msxml2.XMLHTTP") },
			function() { return new ActiveXObject("Microsoft.XMLHTTP") }
		);
	}

	/**
	 * Static
	 */
	jClass.copy($, {
		// fetch empty function
		blank: function() {
			return function() {}
		},
		// collection browser
		foreach: function(items, handler) {
			for(var i = 0, l = items.length; i < l; ++i)
				if (handler(items,i)) // break the loop on TRUE
					return;
		},
		// try each argument
		tryEach: function() {
			var r;
			$.foreach(arguments, function(items, i) {
				try {
					r = items[i]();
					return true;
				} catch (e) {
					return false;
				}
			});
			return r;
		},
		// AJAX request
		AJAX: function(uri, prop) {
			new Transport().request(uri, prop);
		},
		// clear elements cache
		clearElementsCache: function() {
			eCache = {};
		},
		// create a new image
		createImage: function(src, oncomplete) {
			var img = new Image();
			// set handler
			if (oncomplete)
				img.onload = oncomplete;
			img.src = src;
			return img;
		},
		// cookie wrapper
		cake: {
			// server path
			path: '/',
			// server domain
			domain: '',
			// getter
			get: function(name, def) {
				var results = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
				return results ? results[2].decode() : (def || '');
			},
			// setter
			// @param exp - {y,m,d,relative}
			set: function(name, value, exp, path, domain, secure) {

				// cookie string
				var param = name + "=" + escape(value);

				if (exp) {
					var expires;
					if (exp.relative) {
						var now = new Date();
						expires = new Date(now.getFullYear() + exp.y, now.getMonth() + exp.m, now.getDate() + exp.d);
					}
					else
						expires = new Date(exp.y,exp.m,exp.d);
					// save
					param += "; expires=" + expires.toGMTString();
				}

				param += "; path=" + escape(path || $.cake.path) + "; domain=" + escape(domain || $.cake.domain);

				if (secure)
					param += "; secure";

				document.cookie = param;
			}
		},
		// current time
		time: function() {
			return new Date().getTime();
		},
		// check for empty object
		empty: function(obj) {
			return typeof(obj) == "undefined" || obj == null || obj.length == 0;
		},
		// fetch window size
		// @param withScroll
		windowsize: function(withScroll) {

			var e, result;

			// Internet Explorer
			if (navigator.isIE) {
				e = document.documentElement;
				result = [e.clientWidth, e.clientHeight];
			}
			// W3C
			else {
				e = document.body;
				result = [window.innerWidth, window.innerHeight];
			}

			// add scroll offset
			if (withScroll) {
				result[0] += e.scrollLeft;
				result[1] += e.scrollTop;
			}

			return result;
		}
	});

	/**
	 * Static Number Extends
	 * @author ReGeDa
	 */
	jClass.copy(Number, {
		// random number
		random: function() {
			return Math.round(Math.random()*0xfffff);
		}
	});

	/**
	 * Native Boolean Extends
	 * @author ReGeDa
	 */
	jClass.extend(Boolean, {
		// convert to integer
		intval: function() {
			return this == true ? 1 : 0;
		}
	});

	/**
	 * Native Array Extends
	 * @author ReGeDa
	 */
	jClass.extend(Array, {
		// loop each element
		each: function(handler) {
			$.foreach(this, function(items, i) {
				return handler(items[i], i);
			});
		},
		// convert to object
		object: function(fields, indexes) {

			var result = {};

			var self = this;

			// by indexes
			if (indexes) {
				indexes.each(function(index, i) {
					result[fields[i]] = self[index];
				});
			}
			// ordered
			else {
				self.each(function(val, i) {
					result[fields[i]] = val;
				});
			}

			return result;
		}
	});

	/**
	 * Native String Extends
	 * @author ReGeDa
	 */
	jClass.extend(String, {
		// check string length
		empty: function() {
			return this.length == 0;
		},
		// trim a string
		trim: function() {
			return this.replace(/^\s+|\s+$/g,"");
		},
		// parse JSON
		json: function() {
			if (/^[,:{}\[\]0-9.\-+eE \n\r\t]*$/.test(this.
				replace(/\\./g, '@').
				replace(/"[^"\\\n\r]*"|true|false|null/g, ''))) {
				return eval('(' + this + ')');
			}
			throw new SyntaxError("parse JSON");
		},
		// parse integer
		intval: function() {
			var n = parseInt(this);
			return isNaN(n) ? 0 : n;
		},
		// parse float
		floatval: function(precision) {
			return parseFloat(this).toPrecision(precision || 4);
		},
		// URL encode
		encode: function() {
			return encodeURIComponent(this);
		},
		// URL decode
		decode: function() {
			return decodeURIComponent(this);
		},
		// string typography
		typography: function() {
			return this
					.replace(/\.\.\./g,"&hellip;")
					.replace(/\s\-\s/g," &mdash; ")
					.replace(/\-/g," &ndash; ")
					.replace(/</g,"&lt;")
					.replace(/\r?\n/ig,"<br />");
		},
		// format a string
		format: function(args) {
			var s = this;
			$.foreach(args, function(items, i) {
				s = s.replace('{' + i + '}', items[i]);
			});
			return s;
		},
		// convert first letter to upper case
		ucfirst: function() {
			return this.charAt(0).toUpperCase() + this.substr(1);
		}
	});

	/**
	 * Browser type detection
	 * @author ReGeDa
	 */
	var userAgent = navigator.userAgent.toLowerCase();

	// navigator detection
	jClass.copy(navigator, {
		isOpera: /opera/.test(userAgent),
		isIE: /msie/.test(userAgent) && ! /opera/.test(userAgent),
		isSafari: /webkit/.test(userAgent),
		isFF: /firefox/.test(userAgent)
	});

})();

// end of file